/*
 Javascript MD5 library - version 0.2

 coded (2011) by Luigi Galli - LG@4e71.org - http://faultylabs.com

The below code is PUBLIC DOMAIN - NO WARRANTY!

 Changelog: Version 0.2 - 2011-05-22 
            *FIXED: serious integer overflow problems which could cause a wrong MD5 hash being returned

            Version 0.1 - 2011
            -Initial version
*/

var faultylabs = {};

/*
   MD5()

	Computes the MD5 hash for the given input data

    input :  data as String - (Assumes Unicode code points are encoded as UTF-8. If you 
                               attempt to digest Unicode strings using other encodings 
                               you will get incorrect results!)
             data as array of characters - (Assumes Unicode code points are encoded as UTF-8. If you 
                              attempt to digest Unicode strings using other encodings 
                              you will get incorrect results!)
             data as array of bytes
   output: MD5 hash (as Hex String)
*/

faultylabs.MD5 = function(data) {

    // for test/debug
    function fflog(msg) {
        try {
            console.log(msg);
        } catch(e) {}
    }

    // convert number to (unsigned) 32 bit hex, zero filled string
    function to_zerofilled_hex(n) {
        var t1 = (n >>> 24).toString(16);
        var t2 = (n & 0x00FFFFFF).toString(16);
        return "00".substr(0, 2 - t1.length) + t1 +
        "000000".substr(0, 6 - t2.length) + t2;
    }

    // convert array of chars to array of bytes (note: Unicode not supported)
    function chars_to_bytes(ac) {
        var retval = [];
        for (var i = 0; i < ac.length; i++) {
            retval = retval.concat(str_to_bytes(ac[i]));
        }
        return retval;
    }


    // convert a 64 bit unsigned number to array of bytes. Little endian
    function int64_to_bytes(num) {
        var retval = [];
        for (var i = 0; i < 8; i++) {
            retval.push(num & 0xFF);
            num = num >>> 8;
        }
        return retval;
    }

    //  32 bit left-rotation
    function rol(num, places) {
        return ((num << places) & 0xFFFFFFFF) | (num >>> (32 - places));
    }

    // The 4 MD5 functions
    function fF(b, c, d) {
        return (b & c) | (~b & d);
    }

    function fG(b, c, d) {
        return (d & b) | (~d & c);
    }

    function fH(b, c, d) {
        return b ^ c ^ d;
    }

    function fI(b, c, d) {
        return c ^ (b | ~d);
    }

    // pick 4 bytes at specified offset. Little-endian is assumed
    function bytes_to_int32(arr, off) {
        return (arr[off + 3] << 24) | (arr[off + 2] << 16) | (arr[off + 1] << 8) | (arr[off]);
    }

	/*
	Conver string to array of bytes in UTF-8 encoding
	See: 
	http://www.dangrossman.info/2007/05/25/handling-utf-8-in-javascript-php-and-non-utf8-databases/
	http://stackoverflow.com/questions/1240408/reading-bytes-from-a-javascript-string
	How about a String.getBytes(<ENCODING>) for Javascript!? Isn't it time to add it?
	*/
	function str_to_bytes(str) {
		// alert("got " + str.length + " chars")
	    var retval = [ ];
	    for (var i = 0; i < str.length; i++)
	        if (str.charCodeAt(i) <= 0x7F) {
	            retval.push(str.charCodeAt(i));
	        } else {
	            var tmp = encodeURIComponent(str.charAt(i)).substr(1).split('%');
	            for (var j = 0; j < tmp.length; j++) {
	                retval.push(parseInt(tmp[j], 0x10));
				}
	        }
	    return retval;
	};


	

    // convert the 4 32-bit buffers to a 128 bit hex string. (Little-endian is assumed)
    function int128le_to_hex(a, b, c, d) {
        var ra = "";
        var t = 0;
        var ta = 0;
        for (var i = 3; i >= 0; i--) {
            ta = arguments[i];
            t = (ta & 0xFF);
            ta = ta >>> 8;
            t = t << 8;
            t = t | (ta & 0xFF);
            ta = ta >>> 8;
            t = t << 8;
            t = t | (ta & 0xFF);
            ta = ta >>> 8;
            t = t << 8;
            t = t | ta;
            ra = ra + to_zerofilled_hex(t);
        }
        return ra;
    }

    // check input data type and perform conversions if needed
    var databytes = null;
    // String
    if (typeof data == 'string') {
        // convert string to array bytes
        databytes = str_to_bytes(data);
    } else if (data.constructor == Array) {
        if (data.length === 0) {
            // if it's empty, just assume array of bytes
            databytes = data;
        } else if (typeof data[0] == 'string') {
            databytes = chars_to_bytes(data);
        } else if (typeof data[0] == 'number') {
            databytes = data;
        } else {
            fflog("input data type mismatch");
            return null;
        }
    } else {
        fflog("input data type mismatch");
        return null;
    }

    // save original length
    var org_len = databytes.length;

    // first append the "1" + 7x "0"
    databytes.push(0x80);

    // determine required amount of padding
    var tail = databytes.length % 64;
    // no room for msg length?
    if (tail > 56) {
        // pad to next 512 bit block
        for (var i = 0; i < (64 - tail); i++) {
            databytes.push(0x0);
        }
        tail = databytes.length % 64;
    }
    for (i = 0; i < (56 - tail); i++) {
        databytes.push(0x0);
    }
    // message length in bits mod 512 should now be 448
    // append 64 bit, little-endian original msg length (in *bits*!)
    databytes = databytes.concat(int64_to_bytes(org_len * 8));

    // initialize 4x32 bit state
    var h0 = 0x67452301;
    var h1 = 0xEFCDAB89;
    var h2 = 0x98BADCFE;
    var h3 = 0x10325476;

    // temp buffers
    var a = 0,
    b = 0,
    c = 0,
    d = 0;


	function _add(n1, n2) {
		return 0x0FFFFFFFF & (n1 + n2)
	}
	
    // function update partial state for each run
    var updateRun = function(nf, sin32, dw32, b32) {
        var temp = d;
        d = c;
        c = b;
        //b = b + rol(a + (nf + (sin32 + dw32)), b32);
		b = _add(b, 
			rol( 
				_add(a, 
					_add(nf, _add(sin32, dw32))
				), b32
			)
		);
        a = temp;
    };


    // Digest message
    for (i = 0; i < databytes.length / 64; i++) {
        // initialize run
        a = h0;
        b = h1;
        c = h2;
        d = h3;

        var ptr = i * 64;

        // do 64 runs
        updateRun(fF(b, c, d), 0xd76aa478, bytes_to_int32(databytes, ptr), 7);
        updateRun(fF(b, c, d), 0xe8c7b756, bytes_to_int32(databytes, ptr + 4), 12);
        updateRun(fF(b, c, d), 0x242070db, bytes_to_int32(databytes, ptr + 8), 17);
        updateRun(fF(b, c, d), 0xc1bdceee, bytes_to_int32(databytes, ptr + 12), 22);
        updateRun(fF(b, c, d), 0xf57c0faf, bytes_to_int32(databytes, ptr + 16), 7);
        updateRun(fF(b, c, d), 0x4787c62a, bytes_to_int32(databytes, ptr + 20), 12);
        updateRun(fF(b, c, d), 0xa8304613, bytes_to_int32(databytes, ptr + 24), 17);
        updateRun(fF(b, c, d), 0xfd469501, bytes_to_int32(databytes, ptr + 28), 22);
        updateRun(fF(b, c, d), 0x698098d8, bytes_to_int32(databytes, ptr + 32), 7);
        updateRun(fF(b, c, d), 0x8b44f7af, bytes_to_int32(databytes, ptr + 36), 12);
        updateRun(fF(b, c, d), 0xffff5bb1, bytes_to_int32(databytes, ptr + 40), 17);
        updateRun(fF(b, c, d), 0x895cd7be, bytes_to_int32(databytes, ptr + 44), 22);
        updateRun(fF(b, c, d), 0x6b901122, bytes_to_int32(databytes, ptr + 48), 7);
        updateRun(fF(b, c, d), 0xfd987193, bytes_to_int32(databytes, ptr + 52), 12);
        updateRun(fF(b, c, d), 0xa679438e, bytes_to_int32(databytes, ptr + 56), 17);
        updateRun(fF(b, c, d), 0x49b40821, bytes_to_int32(databytes, ptr + 60), 22);
        updateRun(fG(b, c, d), 0xf61e2562, bytes_to_int32(databytes, ptr + 4), 5);
        updateRun(fG(b, c, d), 0xc040b340, bytes_to_int32(databytes, ptr + 24), 9);
        updateRun(fG(b, c, d), 0x265e5a51, bytes_to_int32(databytes, ptr + 44), 14);
        updateRun(fG(b, c, d), 0xe9b6c7aa, bytes_to_int32(databytes, ptr), 20);
        updateRun(fG(b, c, d), 0xd62f105d, bytes_to_int32(databytes, ptr + 20), 5);
        updateRun(fG(b, c, d), 0x2441453, bytes_to_int32(databytes, ptr + 40), 9);
        updateRun(fG(b, c, d), 0xd8a1e681, bytes_to_int32(databytes, ptr + 60), 14);
        updateRun(fG(b, c, d), 0xe7d3fbc8, bytes_to_int32(databytes, ptr + 16), 20);
        updateRun(fG(b, c, d), 0x21e1cde6, bytes_to_int32(databytes, ptr + 36), 5);
        updateRun(fG(b, c, d), 0xc33707d6, bytes_to_int32(databytes, ptr + 56), 9);
        updateRun(fG(b, c, d), 0xf4d50d87, bytes_to_int32(databytes, ptr + 12), 14);
        updateRun(fG(b, c, d), 0x455a14ed, bytes_to_int32(databytes, ptr + 32), 20);
        updateRun(fG(b, c, d), 0xa9e3e905, bytes_to_int32(databytes, ptr + 52), 5);
        updateRun(fG(b, c, d), 0xfcefa3f8, bytes_to_int32(databytes, ptr + 8), 9);
        updateRun(fG(b, c, d), 0x676f02d9, bytes_to_int32(databytes, ptr + 28), 14);
        updateRun(fG(b, c, d), 0x8d2a4c8a, bytes_to_int32(databytes, ptr + 48), 20);
        updateRun(fH(b, c, d), 0xfffa3942, bytes_to_int32(databytes, ptr + 20), 4);
        updateRun(fH(b, c, d), 0x8771f681, bytes_to_int32(databytes, ptr + 32), 11);
        updateRun(fH(b, c, d), 0x6d9d6122, bytes_to_int32(databytes, ptr + 44), 16);
        updateRun(fH(b, c, d), 0xfde5380c, bytes_to_int32(databytes, ptr + 56), 23);
        updateRun(fH(b, c, d), 0xa4beea44, bytes_to_int32(databytes, ptr + 4), 4);
        updateRun(fH(b, c, d), 0x4bdecfa9, bytes_to_int32(databytes, ptr + 16), 11);
        updateRun(fH(b, c, d), 0xf6bb4b60, bytes_to_int32(databytes, ptr + 28), 16);
        updateRun(fH(b, c, d), 0xbebfbc70, bytes_to_int32(databytes, ptr + 40), 23);
        updateRun(fH(b, c, d), 0x289b7ec6, bytes_to_int32(databytes, ptr + 52), 4);
        updateRun(fH(b, c, d), 0xeaa127fa, bytes_to_int32(databytes, ptr), 11);
        updateRun(fH(b, c, d), 0xd4ef3085, bytes_to_int32(databytes, ptr + 12), 16);
        updateRun(fH(b, c, d), 0x4881d05, bytes_to_int32(databytes, ptr + 24), 23);
        updateRun(fH(b, c, d), 0xd9d4d039, bytes_to_int32(databytes, ptr + 36), 4);
        updateRun(fH(b, c, d), 0xe6db99e5, bytes_to_int32(databytes, ptr + 48), 11);
        updateRun(fH(b, c, d), 0x1fa27cf8, bytes_to_int32(databytes, ptr + 60), 16);
        updateRun(fH(b, c, d), 0xc4ac5665, bytes_to_int32(databytes, ptr + 8), 23);
        updateRun(fI(b, c, d), 0xf4292244, bytes_to_int32(databytes, ptr), 6);
        updateRun(fI(b, c, d), 0x432aff97, bytes_to_int32(databytes, ptr + 28), 10);
        updateRun(fI(b, c, d), 0xab9423a7, bytes_to_int32(databytes, ptr + 56), 15);
        updateRun(fI(b, c, d), 0xfc93a039, bytes_to_int32(databytes, ptr + 20), 21);
        updateRun(fI(b, c, d), 0x655b59c3, bytes_to_int32(databytes, ptr + 48), 6);
        updateRun(fI(b, c, d), 0x8f0ccc92, bytes_to_int32(databytes, ptr + 12), 10);
        updateRun(fI(b, c, d), 0xffeff47d, bytes_to_int32(databytes, ptr + 40), 15);
        updateRun(fI(b, c, d), 0x85845dd1, bytes_to_int32(databytes, ptr + 4), 21);
        updateRun(fI(b, c, d), 0x6fa87e4f, bytes_to_int32(databytes, ptr + 32), 6);
        updateRun(fI(b, c, d), 0xfe2ce6e0, bytes_to_int32(databytes, ptr + 60), 10);
        updateRun(fI(b, c, d), 0xa3014314, bytes_to_int32(databytes, ptr + 24), 15);
        updateRun(fI(b, c, d), 0x4e0811a1, bytes_to_int32(databytes, ptr + 52), 21);
        updateRun(fI(b, c, d), 0xf7537e82, bytes_to_int32(databytes, ptr + 16), 6);
        updateRun(fI(b, c, d), 0xbd3af235, bytes_to_int32(databytes, ptr + 44), 10);
        updateRun(fI(b, c, d), 0x2ad7d2bb, bytes_to_int32(databytes, ptr + 8), 15);
        updateRun(fI(b, c, d), 0xeb86d391, bytes_to_int32(databytes, ptr + 36), 21);

        // update buffers
        h0 = _add(h0, a);
        h1 = _add(h1, b);
        h2 = _add(h2, c);
        h3 = _add(h3, d);
    }
    // Done! Convert buffers to 128 bit (LE)
    return int128le_to_hex(h3, h2, h1, h0).toUpperCase();
};

faultylabs.MD5.test = function() {
	alert('test!')
    function zassert(num, pre) {
        try {
            console.log("Test " + num + " = " + pre);
        } catch(e) {
            alert("Test " + num + " = " + pre);
        }
    }

	// normal string
	zassert(1, faultylabs.MD5("ABC") == "902FBDD2B1DF0C4F70B4A5D23525E932");
	
	// array of chars
	zassert(2, faultylabs.MD5("ABC".split('')) == "902FBDD2B1DF0C4F70B4A5D23525E932");
	
	// array of bytes
	zassert(3, faultylabs.MD5(new Array(0x41, 0x42, 0x43)) == "902FBDD2B1DF0C4F70B4A5D23525E932");

	// empty string
    zassert(4, faultylabs.MD5("") == "D41D8CD98F00B204E9800998ECF8427E");

	// classic 
    zassert(5, faultylabs.MD5("The quick brown fox jumps over the lazy dog") == "9E107D9D372BB6826BD81D3542A419D6");

    // 65 chars (test 2 iter)
    zassert(6, faultylabs.MD5("0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0") == "3BACC73B24051AD5D3449540CE5965CC");

    // exactly 64 chars
    zassert(7, faultylabs.MD5("0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF") == "E889D82DD111D6315D7B1EDCE2B1B30F");

    // 63 chars
    zassert(8, faultylabs.MD5("0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE") == "5B1B0A095ED206888EEBDD186B47CD5A");

    // 61 chars
    zassert(9, faultylabs.MD5("0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABC") == "86857DE98E471343691975AC21079429");

    // WHH 
    zassert(10, faultylabs.MD5("It was not Halloween. If I were telling a story for amusement's sake, I should probably place it on that night of nights; but this is a true record of my own experiences, and I would not put pen to paper to amuse anyone. No. It was after midnight on the morning of the twenty-first day of January. I was sitting reading, as is often my custom, in my study.") == "F34D4FE452D87AB703BDC19FF0971615");

	// Long
	var txt = "It was not Halloween. If I were telling a story for amusement's sake, I should probably place it on that night of nights; but this is a true record of my own experiences, and I would not put pen to paper to amuse anyone. No. It was after midnight on the morning of the twenty-first day of January. I was sitting reading, as is often my custom, in my study.It was not Halloween. If I were telling a story for amusement's sake, I should probably place it on that night of nights; but this is a true record of my own experiences, and I would not put pen to paper to amuse anyone. No. It was after midnight on the morning of the twenty-first day of January. I was sitting reading, as is often my custom, in my study.It was not Halloween. If I were telling a story for amusement's sake, I should probably place it on that night of nights; but this is a true record of my own experiences, and I would not put pen to paper to amuse anyone. No. It was after midnight on the morning of the twenty-first day of January. I was sitting reading, as is often my custom, in my study.It was not Halloween. If I were telling a story for amusement's sake, I should probably place it on that night Halloween. If I were telling a story for amusement's sake, I should probably place it on that night of nights; but this is a true record of my own experiences, and I would not put pen to paper to amuse anyone. No. It was after midnight on the morning of the twenty-first day of January. I was sitting reading, as is often my custom, in my study.It was not Halloween. If I were telling a story for amusement's sake, I should probably place it on that night reading, as is often my custom, in my study.It was not Halloween. If I were telling a story for amusement's sake, I should probably place it on that night Halloween. If I were telling a story for amusement's sake, I should probably place it on that night of nights; but this is a true record of my own experiences, and I would not put pen to paper to amuse anyone. No. It was after midnight on the morning of the twenty-first day of January. I was sitting reading, as is often my custom, in my study.It was not Halloween. If I were telling a story for amusement's sake, I should probably place it on that night reading, as is often my custom, in my study.It was not Halloween. If I were telling a story for amusement's sake, I should probably place it on that night Halloween. If I were telling a story for amusement's sake, I should probably place it on that night of nights; but this is a true record of my own experiences, and I would not put pen to paper to amuse anyone. No. It was after midnight on the morning of the twenty-first day of January. I was sitting reading, as is often my custom, in my study.It was not Halloween. If I were telling a story for amusement's sake, I should probably place it on that night"
	zassert(11, faultylabs.MD5(txt) == "650843F09B493716A53C94E0F657A428")

	// snowman (assumes UTF-8!)
	zassert(12, faultylabs.MD5("☃") == "48856FAF4534A876ADEADC72AEC53CB2");
};
